Skip to content

Add a promise_test sequencing clarification #17924

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 31, 2019
Merged

Conversation

klausw
Copy link
Contributor

@klausw klausw commented Jul 19, 2019

I got bitten by a nasty race condition where a failed promise_rejects caused teardown logic to run after the next test had already started, interfering with the next test's state. Since this was unexpected, here's a proposed addition to the documentation to make this clearer. Let me know if I'm misunderstanding how this works, or if you think this should be changed in promise_test instead.

(For context, see #17898 which contains a fix to a test helper affected by this.)

I got bitten by a nasty race condition where a failed `promise_rejects` caused teardown logic to run after the next test had already started, interfering with the next test's state. Since this was unexpected, here's a proposed addition to the documentation to make this clearer. Let me know if I'm misunderstanding how this works, or if you think this should be changed in promise_test instead.

(For context, see #17898 which contains a fix to a test helper affected by this.)
@klausw
Copy link
Contributor Author

klausw commented Jul 19, 2019

If I'm remembering right, the sequence turned out to be something like this:

promise_test
  promise = test.step(func, ...)
    Test.prototype.step try
      promise_rejects
        test.unreached_func
      unreached_func
        step_func
          assert_unreached
            assert
              throw
    Test.prototype.step catch
      this.done()
(start next test)

As a result, if the tested promise is part of a chain, the following steps in the chain were happening after the this.done().

@jugglinmike
Copy link
Contributor

How could the case fixed by gh-17898 be handled in promise_test?

@jugglinmike
Copy link
Contributor

In the current (unpatched) version of the code, cleanup logic is located in a fulfillment handler for a Promise supplied to testharness.js. When the Promise rejects (e.g. for those tests that expect failure), then I wouldn't expect the fulfillment handler (and therefore the cleanup logic) to run at all.

That said, it sounds like you were seeing the cleanup code executing. Is that right?

@klausw
Copy link
Contributor Author

klausw commented Jul 19, 2019

How could the case fixed by gh-17898 be handled in promise_test?

The malfunctioning test in my patch was the newly added xrSession_requestReferenceSpace_features.https.html which is a promise_test via xr_promise_test and xr_session_promise_test wrappers. Since Chromium doesn't yet implement a newly added restriction in the spec, the last two tests in that file that expect a rejected promise are failing since the promise isn't being rejected.

If I'm understanding it right, the problem was that the unexpectedly not-rejected promise is indeed leading to a test failure in promise_rejects, but since the promise was successful the .then() cleanup was still being run asynchronously, and it ended up executing after the next test's start since the test harness considered the test to be in a known failed state and complete.

@klausw
Copy link
Contributor Author

klausw commented Jul 19, 2019

To clarify, I'm not sure what the expected behavior is if a test file contains multiple failing test. The overall state was still "Failure" as it should be, but it was very confusing to me that I couldn't consistently get test failures reported for both of the tests where I expected failures since the underlying functionality wasn't implemented yet

Depending on the order of the tests, I was either getting the second test to pass unexpectedly, or having it fail with an unrelated error due to losing its device in the middle of the test. After making the change to use add_cleanup, both fail consistently with the expected failure messages in output.

@jugglinmike
Copy link
Contributor

Whether there's one failing test or multiple, the expected behavior is the same. The part that I'm trying to understand is how and why code in one test was still running after the test had failed.

Sometimes, that happens because a Promise is accidentally discarded. The current (pre-add_cleanup) version of the code does not track the Promise created by testSession.end(), but it sounds like the disconnectAllDevices operation was what you saw executing after-the-fact.

I can't explain that, though. If the harness was correctly reporting a test failure, then that means the Promise was rejected, and since disconnectAllDevices is located in a fulfillment handler, it should not have been invoked at all.

Am I interpreting your experience correctly?

@klausw
Copy link
Contributor Author

klausw commented Jul 19, 2019

it sounds like the disconnectAllDevices operation was what you saw executing after-the-fact.

I can't explain that, though. If the harness was correctly reporting a test failure, then that means the Promise was rejected, and since disconnectAllDevices is located in a fulfillment handler, it should not have been invoked at all.

Am I interpreting your experience correctly?

Not quite, it's confusing since this is an expected failure due to a promise that was supposed to be rejected but actually succeeded.

Specifically, the "Non-immersive session rejects local space if not requested" test calls requestReferenceSpace('local'), and according to the latest WebXR spec that promise is supposed to be rejected, but chromium doesn't implement this restriction yet, so the promise succeeds unexpectedly. The promise_rejects from makeInvalidSpaceTest notices that it didn't get rejected and fails the test, but the underlying promise is still successful and executes the chained .then().

The part I'm not entirely clear on is why the harness doesn't wait for the overall promise chain to resolve, but instead proceeds to this.done() earlier than expected. Not sure if this is a side effect of mixing promises and steps, or if it's related to xr_session_promise_test using a nested new Promise internally. That internal promise is resolved by the time this.done() runs, and I thought that this should cause the overall chain to wait for it, but I'm admittedly fuzzy on the details of how this works.

@klausw
Copy link
Contributor Author

klausw commented Jul 19, 2019

FYI, I initially had the wrong promise-returning call in the previous comment, it's supposed to be requestReferenceSpace('local'), done after a successful requestSession('inline, {}). Edited above, but adding as a comment also since github email doesn't reflect edits.

See also my earlier comment #17924 (comment) where I tried to trace the execution sequence - please take that with a grain of salt since I'm not familiar with the code and tend to get confused by async programming.

Copy link
Member

@sideshowbarker sideshowbarker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor copy-editing nits

@jugglinmike
Copy link
Contributor

After a bit of experimentation, it doesn't look like there's much we can do about this in the framework itself.

I generally recommend reserving APIs like done and step for tests declared with async_test and relying on Promises for asynchronous control flow in tests declared with promise_test. That helps avoid ambiguities like this.

My initial thought was to enforce the recommendation in code by rejecting tests which mix paradigms. This doesn't appear to be feasible because many utilities (e.g. EventWatcher) use the async_test methods internally.

A more lax solution might be to wait for the promise_test to settle even after done has been invoked. That seems likely to cause a lot of existing tests to time out, though.

So I think we'll have to settle (Promise humor) for documentation.

Copy link
Contributor

@jugglinmike jugglinmike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, be aware that a test is considered finished as soon as its status is resolved, and a failed test's chained actions may still be in progress when the next test starts.

The term "status" has a specific meaning in testharness.js, so using it to describe a different concept may confuse readers. The term "actions" isn't defined for the web platform or for testharness.js. Rather than improve the wording, I suggest removing the sentence. The second sentence is what's important for test authors.

@klausw
Copy link
Contributor Author

klausw commented Jul 30, 2019

jugglinmike@ wrote:

I generally recommend reserving APIs like done and step for tests declared with async_test and relying on Promises for asynchronous control flow in tests declared with promise_test. That helps avoid ambiguities like this.

Ah, so was part of the issue that my makeSpaceTest used t.step()? I got the impression that promise_test's internal use of step ended up triggering the earlier-than-expected done() call.

The term "status" has a specific meaning in testharness.js, so using it to describe a different concept may confuse readers. The term "actions" isn't defined for the web platform or for testharness.js. Rather than improve the wording, I suggest removing the sentence. The second sentence is what's important for test authors.

Removed, though I think the result looks a bit odd - I'd intuitively interpret "finished" from the first sentence as being equivalent to "settled", and if that's not the case I think it would be helpful to say so explicitly. Yes, following the instructions would avoid issues, but people may still end up with an incorrect mental model.

I'm OK proceeding as-is with the suggested changes incorporated, but how would you feel about something like this?

  ... don't start running until after the previous Promise Test finishes. 
+ However, a failing test can finish while chained promises are not yet settled.
  Use [add_cleanup](#cleanup) to register ...
  ... don't start running until after the previous Promise Test finishes. 
+ However, a failing test does not wait for the overall `promise_test` to settle, so 
+ code in a `catch`/`finally` branch may run concurrently with the next test.
  Use [add_cleanup](#cleanup) to register ...

@jugglinmike
Copy link
Contributor

This behavior isn't limited to failing tests. Authors risk interleaved execution any time the done method is invoked prior to Promise settling, e.g.

promise_test((t) => {
  t.done();
  return Promise.resolve();
});

We could explain this more thoroughly, but I still think the intricacies will be more distracting than helpful for the first-time contributor. As a middle ground, we could hand-wave the details and reference this discussion.

  ... don't start running until after the previous Promise Test finishes. 
+ [Under rare
+ circumstances](https://github.com/web-platform-tests/wpt/pull/17924), the
+ next test may begin to execute before the returned promise has settled.
  Use [add_cleanup](#cleanup) to register ...

That makes the finer points available to those who care and avoids confusing those who are looking for direct instruction. What do you think?

@klausw klausw dismissed jugglinmike’s stale review July 31, 2019 02:06

This change is included in cf227e2

@klausw
Copy link
Contributor Author

klausw commented Jul 31, 2019

We could explain this more thoroughly, but I still think the intricacies will be more distracting than helpful for the first-time contributor. As a middle ground, we could hand-wave the details and reference this discussion. [...]
That makes the finer points available to those who care and avoids confusing those who are looking for direct instruction. What do you think?

That sounds fine with me, I incorporated that suggestion. Thanks!

Copy link
Contributor

@jugglinmike jugglinmike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

@jugglinmike jugglinmike merged commit 40d421b into master Jul 31, 2019
@gsnedders gsnedders deleted the klausw-patch-1 branch August 1, 2019 12:57
natechapin pushed a commit to natechapin/wpt that referenced this pull request Aug 23, 2019
* Add a promise_test sequencing clarification

I got bitten by a nasty race condition where a failed `promise_rejects` caused teardown logic to run after the next test had already started, interfering with the next test's state. Since this was unexpected, here's a proposed addition to the documentation to make this clearer. Let me know if I'm misunderstanding how this works, or if you think this should be changed in promise_test instead.

(For context, see web-platform-tests#17898 which contains a fix to a test helper affected by this.)

* Delete sentence as suggested, s/needed/necessary/

* Re-wrap paragraph

* Fix "add_cleanup" link

The code font quote overrode the intended linking

* Add jugglinmike@'s proposed clarification

* Remove trailing whitespace
chromium-wpt-export-bot pushed a commit that referenced this pull request May 15, 2020
This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
chromium-wpt-export-bot pushed a commit that referenced this pull request May 15, 2020
This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203378
Auto-Submit: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769442}
chromium-wpt-export-bot pushed a commit that referenced this pull request May 15, 2020
This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203378
Auto-Submit: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769442}
pull bot pushed a commit to FreddyZeng/chromium that referenced this pull request May 16, 2020
This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
web-platform-tests/wpt#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203378
Auto-Submit: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769442}
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request May 26, 2020
…in a cleanup function., a=testonly

Automatic update from web-platform-tests
sensors: Call GenericSensorTest.reset() in a cleanup function.

This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
web-platform-tests/wpt#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203378
Auto-Submit: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769442}

--

wpt-commits: e909362eb429426f97d3c8731855075de9def0b2
wpt-pr: 23629
xeonchen pushed a commit to xeonchen/gecko that referenced this pull request May 26, 2020
…in a cleanup function., a=testonly

Automatic update from web-platform-tests
sensors: Call GenericSensorTest.reset() in a cleanup function.

This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
web-platform-tests/wpt#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203378
Auto-Submit: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769442}

--

wpt-commits: e909362eb429426f97d3c8731855075de9def0b2
wpt-pr: 23629
bhearsum pushed a commit to mozilla-releng/staging-firefox that referenced this pull request May 1, 2025
…in a cleanup function., a=testonly

Automatic update from web-platform-tests
sensors: Call GenericSensorTest.reset() in a cleanup function.

This addresses some of the flakiness in the sensors tests, in addition to
helping make it easier to find other sources of flakiness.

Instead of calling GenericSensorTest.reset() in a `finally` clause, use
t.add_cleanup() instead. The latter's behavior is more deterministic (see
web-platform-tests/wpt#17924), and fixes an issue
where an EventWatcher would fail an assertion when receiving an unexpected
event, and GenericSensorTest.reset() would either not be called or called
after other tests had already started running.

Bug: 731018
Change-Id: Ifbb95b8067b153b70ecb3e6509f476164afd022e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2203378
Auto-Submit: Raphael Kubo da Costa <raphael.kubo.da.costa@intel.com>
Commit-Queue: Reilly Grant <reillyg@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#769442}

--

wpt-commits: e909362eb429426f97d3c8731855075de9def0b2
wpt-pr: 23629
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants